const simpleCharsetRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/

/**
 * Parse the Accept-Charset header.
 * @private
 */
function parseAcceptCharset(accept) {
  var accepts = accept.split(',')

  for(var i = 0, j = 0; i < accepts.length; i++) {
    var charset = parseCharset(accepts[i].trim(), i)

    if(charset) {
      accepts[j++] = charset
    }
  }

  // trim accepts
  accepts.length = j

  return accepts
}

/**
 * Parse a charset from the Accept-Charset header.
 */
function parseCharset(str, i) {
  var match = simpleCharsetRegExp.exec(str)
  if(!match) return null

  var charset = match[1]
  var q = 1
  if(match[2]) {
    var params = match[2].split(';')
    for(var j = 0; j < params.length; j++) {
      var p = params[j].trim().split('=')
      if(p[0] === 'q') {
        q = parseFloat(p[1])
        break
      }
    }
  }

  return {
    charset: charset,
    q: q,
    i: i
  }
}

/**
 * Get the priority of a charset.
 */
function getCharsetPriority(charset, accepted, index) {
  var priority = { o: -1, q: 0, s: 0 }

  for(var i = 0; i < accepted.length; i++) {
    var spec = specify(charset, accepted[i], index)

    if(spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
      priority = spec
    }
  }

  return priority
}

/**
 * Get the specificity of the charset.
 * @private
 */
function specify(charset, spec, index) {
  var s = 0
  if(spec.charset.toLowerCase() === charset.toLowerCase()) {
    s |= 1
  } else if(spec.charset !== '*') {
    return null
  }

  return {
    i: index,
    o: spec.i,
    q: spec.q,
    s: s
  }
}

/**
 * Get the preferred charsets from an Accept-Charset header.
 * @public
 */
export default  function preferredCharsets(accept, provided) {
  // RFC 2616 sec 14.2: no header = *
  var accepts = parseAcceptCharset(accept === undefined ? '*' : accept || '')

  if(!provided) {
    // sorted list of all charsets
    return accepts
      .filter(isQuality)
      .sort(compareSpecs)
      .map(getFullCharset)
  }

  var priorities = provided.map(function getPriority(type, index) {
    return getCharsetPriority(type, accepts, index)
  })

  // sorted list of accepted charsets
  return priorities.filter(isQuality).sort(compareSpecs).map(function getCharset(priority) {
    return provided[priorities.indexOf(priority)]
  })
}

/**
 * Compare two specs.
 * @private
 */

function compareSpecs(a, b) {
  return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0
}

/**
 * Get full charset string.
 * @private
 */

function getFullCharset(spec) {
  return spec.charset
}

/**
 * Check if a spec has any quality.
 * @private
 */

function isQuality(spec) {
  return spec.q > 0
}
